package com.samtech.primefaces.service.impl;

import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import javax.persistence.TemporalType;

import org.springframework.stereotype.Service;

import com.samtech.common.domain.PagingAndSorting;
import com.samtech.common.domain.ParamValue;
import com.samtech.common.service.CommonQueryService;
import com.samtech.common.utils.ClassUtils;
import com.samtech.common.utils.ClassUtils.AnalyzeResult;
@Service("commonQueryService")
public class QueryServiceImpl implements CommonQueryService {

	@PersistenceContext
	protected EntityManager entityManager;

	public EntityManager getEntityManager() {
		return entityManager;
	}

	public void setEntityManager(EntityManager entityManager) {
		this.entityManager = entityManager;
	}

	public <T> T getObject(Class<T> clazz, Serializable id) {
		return entityManager.find(clazz, id);

	}

	@SuppressWarnings("unchecked")
	public <T> List<T> query(final Class<T> clazz, final String sql,
			final Map<String, Object> params, final PagingAndSorting pg) {
		Query query = entityManager.createQuery(PagingAndSorting.buildOrderBy(
				sql, pg));

		if (params != null && !params.isEmpty()) {
			Iterator iter = params.keySet().iterator();
			while (iter.hasNext()) {
				String parameterName = (String) iter.next();
				Object object = params.get(parameterName);
				if (object instanceof ParamValue) {
					ParamValue pv = (ParamValue) object;
					int type = pv.getType();
					if (pv.getValue() instanceof Date
							|| pv.getValue() instanceof Calendar) {
						boolean isDate = false;
						if (pv.getValue() instanceof Date) {
							isDate = true;
						}
						if (type == Types.DATE) {
							if (isDate)
								query.setParameter(parameterName, (Date) pv
										.getValue(), TemporalType.DATE);
							else
								query.setParameter(parameterName, (Calendar) pv
										.getValue(), TemporalType.DATE);
						} else if (type == Types.TIMESTAMP) {
							if (isDate)
								query.setParameter(parameterName, (Date) pv
										.getValue(), TemporalType.TIMESTAMP);
							else
								query.setParameter(parameterName, (Calendar) pv
										.getValue(), TemporalType.TIMESTAMP);
						} else if (type == Types.TIME) {
							if (isDate)
								query.setParameter(parameterName, (Date) pv
										.getValue(), TemporalType.TIME);
							else
								query.setParameter(parameterName, (Calendar) pv
										.getValue(), TemporalType.TIME);
						} else {
							query.setParameter(parameterName, pv.getValue());
						}
					} else {
						query.setParameter(parameterName, pv.getValue());
					}

				} else if (object instanceof Date) {

					query.setParameter(parameterName, (Date) object,
							TemporalType.DATE);
				} else if (object instanceof Collection) {
					query.setParameter(parameterName, (Collection) object);
				} else {
					query.setParameter(parameterName, object);
				}

			}
		}
		if (pg != null && pg.getStart() >= 0) {
			query.setFirstResult(pg.getStart());
			if (pg.getEnd() != -1) {
				query.setMaxResults(pg.getEnd() - pg.getStart() + 1);
			}
			query.setHint("org.hibernate.fetchSize", new Integer(
					PagingAndSorting.getFetchSize(pg.getStart())));
		}
		List s = query.getResultList();
		if (s == null)
			return Collections.EMPTY_LIST;

		if (s instanceof List) {
			ArrayList<T> results = new ArrayList<T>();
			List lst = (List) s;
			boolean found = true;
			AnalyzeResult analyzeResult = ClassUtils.getAnalyzeResult(clazz);
			for (Object t : lst) {
				Map<Integer, String> fieldNames = new HashMap<Integer, String>();
				if (t != null) {
					if (t.getClass().isAssignableFrom(clazz)) {
						found = true;
						break;
					} else if (t.getClass().isArray()) {
						if (fieldNames.isEmpty()) {
							int select_idx = sql.toUpperCase().indexOf(
									"SELECT ");
							int from_idx = -1;
							if (select_idx >= 0) {
								from_idx = sql.toUpperCase().indexOf(" FROM ",
										select_idx);
							}
							if (select_idx >= 0 && from_idx >= 0) {
								String substring = sql.substring(select_idx
										+ "select".length(), from_idx);
								String[] split = substring.split(",");
								for (int k = 0; k < split.length; k++) {
									int as_idx = split[k].toUpperCase()
											.indexOf(" AS ");
									if (as_idx >= 0) {
										fieldNames.put(new Integer(k), split[k]
												.substring(as_idx + 3).trim());
									} else
										fieldNames.put(new Integer(k), split[k]
												.trim());
								}
							} else
								throw new RuntimeException("convert exception");
						}
						if (!fieldNames.isEmpty()) {
							try {
								T instance = clazz.newInstance();
								Object[] arrs = (Object[]) t;
								for (int k = 0; k < arrs.length; k++) {
									String field = matchField(fieldNames
											.get(new Integer(k)),
											(String[]) analyzeResult
													.getFieldNames().toArray());
									if (field != null) {
										Map<String, Method> writeMethod = analyzeResult
												.getWriteMethod();
										if (writeMethod.containsKey(field)) {
											Method method = writeMethod
													.get(field);
											Class<?>[] parameterTypes = method
													.getParameterTypes();
											Object object = arrs[k];
											try {
												object = convertType(
														parameterTypes[0],
														object);
												method
														.invoke(
																instance,
																new Object[] { object });
											} catch (IllegalArgumentException e) {
												e.printStackTrace();
											} catch (InvocationTargetException e) {
												e.printStackTrace();
											}
										}
									} else if (analyzeResult.getIdFieldNames() != null) {
										field = matchField(fieldNames
												.get(new Integer(k)),
												analyzeResult.getIdFieldNames());
										if (field != null) {
											Class<?> idClass = analyzeResult
													.getIdClass();
											Map<String, Method> parentmethods = analyzeResult
													.getReadMethod();
											Method m = parentmethods
													.get(analyzeResult
															.getIdFieldName());
											Object idObject = null;
											try {
												idObject = m.invoke(instance,
														new Object[0]);
												if (idObject == null) {
													idObject = idClass
															.newInstance();
													parentmethods = analyzeResult
															.getWriteMethod();
													m = parentmethods
															.get(analyzeResult
																	.getIdFieldName());
													m
															.invoke(
																	instance,
																	new Object[] { idObject });
												}
											} catch (IllegalArgumentException e1) {
												e1.printStackTrace();
											} catch (InvocationTargetException e1) {
												e1.printStackTrace();
											}
											AnalyzeResult idresult = ClassUtils
													.getAnalyzeResult(idClass);
											Map<String, Method> writeMethod = idresult
													.getWriteMethod();
											if (writeMethod.containsKey(field)) {
												Method method = writeMethod
														.get(field);
												Class<?>[] parameterTypes = method
														.getParameterTypes();
												Object object = arrs[k];
												try {
													object = convertType(
															parameterTypes[0],
															object);
													method
															.invoke(
																	idObject,
																	new Object[] { object });
												} catch (IllegalArgumentException e) {
													e.printStackTrace();
												} catch (InvocationTargetException e) {
													e.printStackTrace();
												}
											}
										}
									}
								}
								results.add(instance);
							} catch (InstantiationException e) {
								e.printStackTrace();
							} catch (IllegalAccessException e) {
								e.printStackTrace();
							}

						}

					}
				}
			}
			if (found)
				return lst;

			return results;
		}
		if (s.getClass().isArray()) {
			List<T> asList = (List<T>) Arrays.asList(s);
			return asList;
		}
		List ls = new ArrayList(1);
		ls.add(s);
		return ls;

	}

	protected static String matchField(String fieldName, String[] fields) {
		if (fields != null) {
			for (int i = 0; i < fields.length; i++) {
				if (fields[i].equals(fieldName))
					return fieldName;
			}
		}
		return null;
	}

	protected static Object convertType(Class<?> clazz, Object o) {
		if (o == null)
			return o;
		if (o.getClass().isAssignableFrom(clazz))
			return o;
		if (Long.class.equals(clazz)) {
			return new Long(o.toString());
		}
		if (Integer.class.equals(clazz)) {
			return new Integer(o.toString());
		}
		if (BigInteger.class.equals(clazz)) {
			return new BigInteger(o.toString());
		}
		if (BigDecimal.class.equals(clazz)) {
			return new BigDecimal(o.toString());
		}
		if (Date.class.equals(clazz)) {
			if (o instanceof Date) {
				Date date = (Date) o;
				Calendar cld = Calendar.getInstance();
				cld.setTimeInMillis(date.getTime());
				return cld.getTime();
			}
		}

		return o;
	}
}
